package android.matrix;

import android.remotematrixclient.objclient.ObjClient;
import android.remotematrixclient.objclient.clientevent.ObjEvent;
import android.remotematrixclient.objclient.clientevent.ObjEventListener;
import android.util.Log;

/**
 * La <code> RemoteMatrix</code> es una subclase de <code>AbstractMatrix</code>
 * describe la creación de una matriz numérica para realizar operaciones en un 
 * servidor de ejecución remota concurrente que  brinda un servicio en tiempo 
 * real. Las instancias de esta clase envían datos a través de un servicio de 
 * transferencia de objetos y utiliza una clase envoltorio para encapsular los 
 * datos relevantes que le permitan crear una instancia similar en el otro 
 * extremo.
 * 
 * @author Rafael Asprilla
 */

public class RemoteMatrix extends AbstractMatrix implements MatrixOps{
	
	
	/**
	 * Identificador de la transacción remota actual. Cambia cada vez que se 
	 * genera una nueva transacción.
	 */
	private int transaction_id;
	
	/**
	 * Estatus de la sincronización con el servidor remoto. Si la operación de
	 * sincronización de los datos fue exitosa y existe una copia en el
	 * servidor remoto entonces <code>sync = true</code> en cualquier otro caso
	 * <code>sync = false</code>.
	 */
	private boolean sync;
	
	/**
	 * Define si la transacción activa fue completada o no. Sólo se puede
	 * realizar una transacción a la vez debido a las posibles condiciones de
	 * carrera que podrían alterar erróneamente el contenido de la matriz.
	 */
	private boolean done;
	
	/**
	 * Constructor de la clase <code>RemoteMatrix</code>. Construye una matriz
	 * a partir de cualquier otro tipo de matriz subclase de 
	 * <code>AbstracMatrix</code>. Objtiene los valores básicos en el arreglo 
	 * de valores flotantes, el nombre de archivo y los copia dentro de la 
	 * nueva matriz. Igual se incrementa la cuenta de objetos de matriz, 
	 * creando un nuevo identificador de objeto matriz para esta nueva
	 * instancia.
	 * 
	 * @param m
	 * Matriz de tipo <code>AbstractMatrix</code> que se quiere convertir en 
	 * tipo <code>RemoteMatrix</code>.
	 */
	public RemoteMatrix(AbstractMatrix m)
	{
		this(m.element);
	}

	/**
	 * Constructor de la clase <code>RemoteMatrix</code> accepta los valores 
	 * predefinidos de los elementos de la matriz. Los valores están en punto 
	 * flotante para aceptar todo tipo se matrices numéricas. 
	 * 
	 * El constructor también genera un nombre de archivo por defecto para la 
	 * matriz actual que contiene una referencia númerica al número de 
	 * instancias creadas hasta el momento. 
	 * 
	 * @param element 
	 * Valores de los elementos de la matriz.
	 * @throws IOException 
	 * Esta excepción se genera si existe algún problema con los flujos de 
	 * archivos.
	 */
	public RemoteMatrix(float[][] element)
	{
		super(element);
		sync = false;
		done = false;
		MatrixStub ms = new MatrixStub(this.element, this.filename);
		transaction_id = ObjClient.getInstance().PUT(ms);
		
		ObjClient.getInstance().addListener(new ObjEventListener(){

			@Override
			public void handleClientEvent(ObjEvent evt) {
				if(evt.getID() == transaction_id){
					sync = evt.isSuccess();
					Log.d("RemoteMatrix", "Synchronization callback, command result: " + sync);
					done = true;
					if(sync)
						ObjClient.getInstance().removeListener(this);
				}
			}
			
		});
	}
	
	/**
	 * Constructor de la clase <code>RemoteMatrix</code>, este constructor 
	 * genera una instancia del objeto a partir del archivo cuyo nombre se
	 * encuentra en el parámetro <code>filename</code>, que se encuentra 
	 * almacenado en el servidor remoto. Una vez se crea la transacción
	 * se contacta al servidor remoto y se intenta descargar el archivo, si el
	 * archivo no existe se envía un mensaje con una respuesta de fallo, de 
	 * otro modo se envía un objeto de tipo <code>MatrixStub</code> con los 
	 * datos de la matriz que se solicitó.
	 * 
	 * @param filename 
	 * Archivo de matrices que debe ser leido para crear el objeto 
	 * <code>RemoteMatrix</code>.
	 * @throws IOException 
	 * Esta excepción se genera si existe algún problema con los flujos de 
	 * archivos.
	 */
	public RemoteMatrix(String filename)
	{
		super();
		sync = false;
		done = false;
		this.filename = filename;
		transaction_id = ObjClient.getInstance().GET(filename);

		
		ObjClient.getInstance().addListener(new ObjEventListener(){

			@Override
			public void handleClientEvent(ObjEvent evt) {
				
				if(evt.getID() == transaction_id){
					sync = evt.isSuccess();
					
					if(sync){
						MatrixStub ms = (MatrixStub) evt.getData();
						set(ms);
						ObjClient.getInstance().removeListener(this);
					}
					
					done = true;
				}
			}
		});
	}
	
	/**
	 * Constructor dummy de la clase <code>RemoteMatrix</code>. Crea una matriz
	 * vacía para que pueda ser modificada por uno de los métodos de la clase
	 * <code>RemoteMatrix</code>.
	 */
	public RemoteMatrix() {
		super();
	}

	/**
	 * Devuelve el estatus de la transacción de sincronización.
	 * @return 
	 * Devuelve cierto si la operación de sincronización fue exitosa. Falso de
	 * otro modo.
	 */
	public boolean isSync() {
		return sync;
	}
	
	/**
	 * Devuelve el estatus de culminación de la transacción actual.
	 * @return
	 * Devuelve cierto si la operación se ha completado. Falso de otro modo.
	 */
	public boolean isDone(){
		return done;
	}
	
	/**
	 * El método privado <code>set(MatrixStub)</code> almacena los datos 
	 * recibidos del servidor remoto para agregarlos a una matriz vacía.
	 * 
	 * @param ms
	 * Datos de la matriz a ser modificada.
	 */
	private void set(MatrixStub ms){
		
		this.element = ms.element;
		this.rows = element.length;
		this.columns = element[0].length;		
	}

	/**
	 * El método <code>O(int, int)</code> genera una matriz de ceros con las 
	 * dimensiones <code>rows</code> y <code>columns</code>.
	 * 
	 * @param rows 
	 * Número de filas de la matriz O.
	 * @param columns 
	 * Número de columnas de la matriz O.
	 * @return
	 * Retorna una matriz de ceros O[rows][columns];
	 * @throws IOException 
	 * Esta excepción se genera si existe algún problema con los flujos de 
	 * archivos.
	 */
	public static RemoteMatrix O(int rows, int columns)
	{
		int i;
		float element[][] = new float[rows][columns];
		for(i = 0; i < rows; i++)
			for(int j = 0; j < columns; j++)
				element[i][j] = (float) 0; 
		RemoteMatrix x = new RemoteMatrix(element);
		return x;
	}
	
	/**
	 * El método <code>U(int, int)</code> genera una matriz de unos con las 
	 * dimensiones <code>rows</code> y <code>columns</code>.
	 * 
	 * @param rows 
	 * Número de filas de la matriz U.
	 * @param columns 
	 * Número de columnas de la matriz U.
	 * @return
	 * Retorna una matriz de unos U[rows][columns];
	 * @throws IOException 
	 * Esta excepción se genera si existe algún problema con los flujos de 
	 * archivos.
	 */
	public static RemoteMatrix U(int rows, int columns)
	{
		int i;
		float element[][] = new float[rows][columns];
		for(i = 0; i < rows; i++)
			for(int j = 0; j < columns; j++)
				element[i][j] = (float) 1; 
		RemoteMatrix x = new RemoteMatrix(element);
		return x;
	}
	
	/**
	 * El método <code>estático I(int)</code> genera una matriz identidad 
	 * cuadrada. La matriz identidad está compuesta de ceros excepto en su
	 * diagonal principal.
	 * 
	 * @param rows
	 * Número de filas y columnas de la matriz I.
	 * @return
	 * Retorna una matriz identidad I[rows][rows].
	 * @throws IOException 
	 * Esta excepción se genera si existe algún problema con los flujos de 
	 * archivos.
	 */
	public static RemoteMatrix I(int rows)
	{
		int i;
		float element[][] = new float[rows][rows];
		for(i = 0; i < rows; i++)
			for(int j = 0; j < rows; j++)
				if(i == j)
					element[i][j] = (float) 1; 
				else
					element[i][j] = (float) 0;
		RemoteMatrix x = new RemoteMatrix(element);
		return x;
	}
	
	/**
	 * El método <code>getFileName()</code> devuelve el nombre de archivo 
	 * predeterminado en el momento de la construcción del objeto.
	 * 
	 * @return
	 * Retorna el valor del atributo filename.
	 */
	public String getFileName() 
	{
		return filename;
	}


	/* (non-Javadoc)
	 * @see android.matrix.MatrixOps#mult(android.matrix.AbstractMatrix)
	 */
	@Override
	public RemoteMatrix mult(AbstractMatrix b)
	{
		RemoteMatrix c;
		sync = false;
		done = false;
		if(b instanceof RemoteMatrix)
			c = (RemoteMatrix) b;
		else
			c = new RemoteMatrix(b);
		
		while(!this.isSync() && !c.isSync());
		
		String outfn = String.format("mult%03d%03d.mat", this.matnum, c.matnum);
		String cmd = String.format("%s %s %s %d", outfn, this.filename, c.filename, ObjClient.getInstance().get_cpus());
		transaction_id = ObjClient.getInstance().EXE(cmd);
		
		ObjClient.getInstance().addListener(new ObjEventListener(){
			
			@Override
			public void handleClientEvent(ObjEvent evt) {
				if(evt.getID() == transaction_id){
					sync = evt.isSuccess();
					
					if(sync)
						ObjClient.getInstance().removeListener(this);
					
					done = true;
				}
			}
			
		});
		
		while(!done);
		
		return new RemoteMatrix(outfn);
		
	}

	/* (non-Javadoc)
	 * @see android.matrix.MatrixOps#sum(android.matrix.AbstractMatrix)
	 */
	@Override
	public RemoteMatrix sum(AbstractMatrix b)
	{
		return null;
	}

	/* (non-Javadoc)
	 * @see android.matrix.MatrixOps#sub(android.matrix.AbstractMatrix)
	 */
	@Override
	public RemoteMatrix sub(AbstractMatrix b)
	{
		return null;
	}

	/* (non-Javadoc)
	 * @see android.matrix.MatrixOps#scalar(float)
	 */
	@Override
	public RemoteMatrix scalar(float c){
		return null;
	}

	/* (non-Javadoc)
	 * @see android.matrix.MatrixOps#transpose()
	 */
	@Override
	public AbstractMatrix transpose() {
		// TODO Auto-generated method stub
		return null;
	}
	
}

